package com.yylc.cscore.component;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.yylc.cscore.dao.TaskDao;
import com.yylc.cscore.dao.TaskRelationDao;
import com.yylc.cscore.dao.TaskRuleDao;
import com.yylc.cscore.dao.TaskTypeDao;
import com.yylc.cscore.entity.TaskEntity;
import com.yylc.cscore.entity.TaskRelationEntity;
import com.yylc.cscore.entity.TaskRuleEntity;
import com.yylc.cscore.entity.TaskTypeEntity;
import com.yylc.cscore.util.JobTriggerThreadFactory;
import com.yylc.cscore.util.R;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toList;

/**
 * @author xiangnan
 * date 2018/5/10
 */
@Component
public class JobTrigger {
    private static final Logger logger = LogManager.getLogger();

    @Resource
    private TaskRuleDao taskRuleDao;

    @Resource
    private TaskDao taskDao;

    @Resource
    private TaskRelationDao taskRelationDao;

    @Resource
    private TaskTypeDao taskTypeDao;

    /**
     * 任务分配线程池，size为1，避免并发执行任务分配（自动/手动）导致的数据库中与任务分配相关的数据不一致问题
     */
    @SuppressWarnings("unchecked")
    private ExecutorService executor =  new ThreadPoolExecutor(1, 1, 60L, TimeUnit.HOURS,
            new LinkedBlockingQueue(), new JobTriggerThreadFactory());

    /**
     * 任务规则服务
     */
    public void trigger(Integer id, Date time) {
        executor.execute(() -> triggerInternal(id, time));
    }

    /**
     * 任务规则服务
     *
     * notice: 这里的事务操作代价是挺大的，为了保证数据OK暂时先上事务
     */
    @Transactional
    public void triggerInternal(Integer id, Date time) {
        TaskRuleEntity entity = taskRuleDao.selectById(id);
        if (entity == null) {
            logger.error("找不到任务规则: id={}", id);
            return;
        }

        if (entity.getStatus() == null || (entity.getType() == R.ruleType.FEN_PEI && entity.getStatus() != R.taskRuleStatus.RUNNING)) {
            logger.error("任务规则未处于有效状态，status={}", entity.getStatus());
            return;
        }

        if (entity.getType() == R.ruleType.HUI_SHOU) {
            recycleTask(entity);
            return;
        }

        triggerInternal(entity, time);
    }

    @Transactional
    public void triggerInternal(TaskRuleEntity entity, Date time) {
        if (entity == null) {
            return;
        }

        // 计算昨天的dayId，格式 yyyyMMdd
        //String dayId = DateUtil.format(new Date(time.getTime() - 24 * 60 * 60 * 1000), "yyyyMMdd");

        // 按照任务类型优先级对待分配任务进行动态分配
        List<String> codeList = StrUtil.isBlank(entity.getAllotTaskCode()) ?
                new ArrayList<>() : CollectionUtil.newArrayList(StrUtil.split(entity.getAllotTaskCode().trim(), R.partFlag));

        logger.info("开始分配任务，taskCodeList={}， serviceIdList={}, time={}", codeList, entity.getAllotServiceId(), time);
        codeList.forEach(code -> {
            List<TaskEntity> taskList = taskDao.selectByDayId(entity.getUserType(), null,
                    R.allotStatus.WAIT, R.operateStatus.INIT, code);

            allotTask(filterTaskList(taskList), entity);
        });
        logger.info("分配任务完成");
    }

    /**
     * 手工将某些未分配用户分配给指定客服人员
     */
    public void triggerInternal(TaskRuleEntity entity, Date time, String userIdList) {
        if (StrUtil.isBlank(userIdList)) {
            triggerInternal(entity, time);
            return;
        }

        List<String> codeList = taskTypeDao.selectList().stream().map(TaskTypeEntity::getTaskCode).collect(toList());
        logger.info("开始分配任务，taskCodeList={}， serviceIdList={}, time={}", codeList, entity.getAllotServiceId(), time);

        codeList.forEach(code -> {
            List<TaskEntity> taskList = taskDao.selectByDayId(entity.getUserType(), null,
                    R.allotStatus.WAIT, R.operateStatus.INIT, code);

            List<TaskEntity> list = filterTaskList(taskList);
            list = list.stream().filter(item -> userIdList.contains(item.getUserId())).collect(toList());
            allotTask(list, entity);
        });
        logger.info("分配任务完成");
    }

    /**
     * 过滤待分配的任务列表，如果该任务对应的用户已经分配给客服了，则过滤掉
     */
    private List<TaskEntity> filterTaskList(List<TaskEntity> list) {
        Set<String> userIdSet = taskRelationDao.selectAllUserId();
        List<TaskEntity> taskList = new ArrayList<>();

        list.forEach(task -> {
            if (!userIdSet.contains(task.getUserId())) {
                taskList.add(task);
            }
        });
        return taskList;
    }

    /**
     * 针对同一种问题类型进行任务分配
     * 注意，taskList参数需要经过过滤
     */
    private void allotTask(List<TaskEntity> taskList, TaskRuleEntity rule) {

        // taskList进行userId去重处理 <userId, TaskEntity>临时缓存
        HashMap<String, TaskEntity> cacheMap = new HashMap<>();
        taskList.forEach(task -> {
            if (cacheMap.containsKey(task.getUserId())) {
                taskList.remove(cacheMap.get(task.getUserId()));
            }
            cacheMap.put(task.getUserId(), task);
        });

        List<String> serviceIdList =  StrUtil.isBlank(rule.getAllotServiceId()) ?
                new ArrayList<>() : CollectionUtil.newArrayList(StrUtil.split(rule.getAllotServiceId().trim(), R.partFlag));
        if (serviceIdList.isEmpty()) {
            return;
        }

        // 客服平均分配任务，最大分配total个任务
        int total = rule.getAllotment() * serviceIdList.size();
        for (int i = 0; i < total && i < taskList.size(); i++) {
            TaskEntity task = taskList.get(i);

            try {
                String serviceId = serviceIdList.get(i % serviceIdList.size());
                taskRelationDao.insert(new TaskRelationEntity(task.getUserId(), serviceId));
            } catch (Exception e) {
                // not too care 这里不会触发事务回滚
                logger.error("平均分配任务异常，task={}, e={}", task, e);
            }
        }
    }

    /**
     * 任务回收，只回收在待回收客服ID列表中客服下的任务
     */
    private void recycleTask(TaskRuleEntity entity) {
        String serviceIds = StrUtil.isBlank(entity.getAllotServiceId()) ? "" : entity.getAllotServiceId();

        logger.info("recycleTask for {}", entity.getAllotServiceId());
        taskRelationDao.selectWhList(R.whLabel.WEI_WAI_HU).forEach(item -> {
            if (serviceIds.contains(item.getServiceId())) {
                taskRelationDao.deleteByIdAndWhLabel(item);
            }
        });
    }

}
